package org.asiainfo.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.tree.Tree;
import com.mybatisflex.core.query.QueryMethods;
import org.asiainfo.common.core.constant.UserConstants;
import org.asiainfo.common.core.utils.StreamUtils;
import org.asiainfo.common.core.utils.StringUtils;
import org.asiainfo.common.core.utils.TreeBuildUtils;
import org.asiainfo.common.satoken.utils.LoginHelper;
import org.asiainfo.system.domain.*;
import org.asiainfo.system.domain.table.*;
import org.asiainfo.system.domain.vo.MetaVo;
import org.asiainfo.system.domain.vo.RouterVo;
import org.asiainfo.system.domain.vo.SysMenuVo;
import org.asiainfo.system.domain.bo.SysMenuBo;
import org.asiainfo.system.mapper.SysRoleMapper;
import org.asiainfo.system.mapper.SysRoleMenuMapper;
import org.asiainfo.system.mapper.SysTenantPackageMapper;
import org.asiainfo.system.service.SysMenuService;
import org.springframework.stereotype.Service;
import org.asiainfo.common.core.utils.MapstructUtils;
import lombok.extern.slf4j.Slf4j;
import cn.hutool.core.util.ObjectUtil;
import com.mybatisflex.core.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.asiainfo.system.mapper.SysMenuMapper;

import java.util.*;

/**
 * 菜单权限表(SysMenu)表服务实现类
 *
 * @author dotor-ww
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {

    private final SysRoleMapper sysRoleMapper;

    private final SysRoleMenuMapper sysRoleMenuMapper;

    private final SysTenantPackageMapper sysTenantPackageMapper;

    /**
     * 根据用户ID查询菜单列表
     *
     * @param sysMenuBo 菜单信息
     * @return 菜单列表
     */
    @Override
    public List<SysMenuVo> selectMenuList(SysMenuBo sysMenuBo) {
        Long userId = LoginHelper.getUserId();
        List<SysMenuVo> sysMenuVos;
        if (LoginHelper.isSuperAdmin(userId)) {
            sysMenuVos = listAs(query()
                .eq(SysMenu::getVisible, sysMenuBo.getVisible(), StringUtils.isNotBlank(sysMenuBo.getVisible()))
                .eq(SysMenu::getStatus, sysMenuBo.getStatus(), StringUtils.isNotBlank(sysMenuBo.getStatus()))
                .like(SysMenu::getMenuName, sysMenuBo.getMenuName(), StringUtils.isNotBlank(sysMenuBo.getMenuName()))
                .orderBy(SysMenu::getParentId).asc()
                .orderBy(SysMenu::getOrderNum).asc(), SysMenuVo.class);
        } else {
            sysMenuVos = queryChain()
                .select(QueryMethods.distinct(SysMenuTableDef.SYS_MENU.DEFAULT_COLUMNS))
                .from(SysMenu.class)
                .leftJoin(SysRoleMenu.class)
                .on(SysMenuTableDef.SYS_MENU.MENU_ID.eq(SysRoleMenuTableDef.SYS_ROLE_MENU.MENU_ID))
                .leftJoin(SysRole.class)
                .on(SysRoleTableDef.SYS_ROLE.ROLE_ID.eq(SysRoleMenuTableDef.SYS_ROLE_MENU.ROLE_ID))
                .where(SysRoleTableDef.SYS_ROLE.ROLE_ID.in(
                    query()
                        .select(SysUserRoleTableDef.SYS_USER_ROLE.ROLE_ID)
                        .from(SysUserRole.class)
                        .where(SysUserRoleTableDef.SYS_USER_ROLE.USER_ID.eq(userId))
                ))
                .and(SysMenuTableDef.SYS_MENU.VISIBLE.eq(sysMenuBo.getVisible(), StringUtils.isNotBlank(sysMenuBo.getVisible())))
                .and(SysMenuTableDef.SYS_MENU.MENU_NAME.like(sysMenuBo.getMenuName(), StringUtils.isNotBlank(sysMenuBo.getMenuName())))
                .and(SysMenuTableDef.SYS_MENU.STATUS.eq(sysMenuBo.getStatus(), StringUtils.isNotBlank(sysMenuBo.getStatus())))
                .listAs(SysMenuVo.class);
        }
        return sysMenuVos;
    }

    /**
     * 构建前端所需要下拉树结构
     *
     * @param menuVos 菜单列表
     * @return 下拉菜单列表
     */
    @Override
    public List<Tree<Long>> buildMenuTreeSelect(List<SysMenuVo> menuVos) {
        if (CollUtil.isEmpty(menuVos)) {
            return CollUtil.newArrayList();
        }
        return TreeBuildUtils.build(menuVos, (menu, tree) -> {
            tree.setId(menu.getMenuId());
            tree.setParentId(menu.getParentId());
            tree.setName(menu.getMenuName());
            tree.setWeight(menu.getOrderNum());
        });
    }

    /**
     * 是否存在菜单子节点
     *
     * @param menuId 菜单ID
     * @return 查询结果
     */
    @Override
    public Boolean hasChildMenuByMenuId(Long menuId) {
        return exists(query().eq(SysMenu::getParentId, menuId));
    }

    /**
     * 是否存在角色菜单权限
     *
     * @param menuId 菜单ID
     * @return 查询结果
     */
    @Override
    public Boolean checkMenuExistRole(Long menuId) {
        return CollUtil.isNotEmpty(sysRoleMenuMapper.selectListByQuery(query().eq(SysRoleMenu::getMenuId, menuId)));
    }

    /**
     * 校验菜单名称是否唯一
     *
     * @param sysMenuBo 菜单信息
     * @return 校验结果
     */
    @Override
    public Boolean checkMenuNameUnique(SysMenuBo sysMenuBo) {
        return exists(query().eq(SysMenu::getMenuName, sysMenuBo.getMenuName())
            .eq(SysMenu::getParentId, sysMenuBo.getParentId())
            .ne(SysMenu::getMenuId, sysMenuBo.getMenuId(), ObjectUtil.isNotNull(sysMenuBo.getMenuId())));
    }

    /**
     * 租户套餐下的菜单列表
     *
     * @param packageId 租户套餐ID
     * @return 菜单列表
     */
    @Override
    public List<Long> selectMenuListByPackageId(Long packageId) {
        SysTenantPackage sysTenantPackage = sysTenantPackageMapper.selectOneById(packageId);
        List<Long> menuIds = StringUtils.splitTo(sysTenantPackage.getMenuIds(), Convert::toLong);
        if (menuIds.size() == 0) {
            return new ArrayList<>();
        }
        List<Long> parentIds = CollUtil.newArrayList();
        if (sysTenantPackage.getMenuCheckStrictly()) {
            QueryWrapper query = query().select(SysMenuTableDef.SYS_MENU.PARENT_ID)
                .from(SysMenu.class)
                .where(SysMenuTableDef.SYS_MENU.MENU_ID.in(menuIds));
            parentIds = listAs(query, Long.class);
        }
        QueryWrapper query = query().select(SysMenuTableDef.SYS_MENU.PARENT_ID)
            .from(SysMenu.class)
            .where(SysMenuTableDef.SYS_MENU.MENU_ID.in(menuIds))
            .and(SysMenuTableDef.SYS_MENU.MENU_ID.notIn(parentIds));
        return listAs(query, Long.class);
    }

    /**
     * 根据用户ID查询权限
     *
     * @param userId 用户ID
     * @return 权限列表
     */
    @Override
    public Set<String> selectMenuPermsByUserId(Long userId) {
        List<String> perms = queryChain()
            .select(QueryMethods.distinct(SysMenuTableDef.SYS_MENU.PERMS))
            .from(SysMenu.class)
            .leftJoin(SysRoleMenu.class)
            .on(SysMenuTableDef.SYS_MENU.MENU_ID.eq(SysRoleMenuTableDef.SYS_ROLE_MENU.MENU_ID))
            .leftJoin(SysUserRole.class)
            .on(SysRoleMenuTableDef.SYS_ROLE_MENU.ROLE_ID.eq(SysUserRoleTableDef.SYS_USER_ROLE.ROLE_ID))
            .leftJoin(SysRole.class)
            .on(SysRoleTableDef.SYS_ROLE.ROLE_ID.eq(SysUserRoleTableDef.SYS_USER_ROLE.ROLE_ID))
            .where(SysMenuTableDef.SYS_MENU.STATUS.eq(UserConstants.MENU_NORMAL))
            .and(SysUserRoleTableDef.SYS_USER_ROLE.USER_ID.eq(userId)).listAs(String.class);
        Set<String> permsSet = new HashSet<>();
        for (String perm : perms) {
            if (StringUtils.isNotEmpty(perm)) {
                permsSet.addAll(StringUtils.splitList(perm.trim()));
            }
        }
        return permsSet;
    }

    /**
     * 根据角色ID查询菜单树信息
     *
     * @param roleId 角色ID
     * @return 菜单列表
     */
    @Override
    public List<Long> selectMenuListByRoleId(Long roleId) {
        SysRole sysRole = sysRoleMapper.selectOneById(roleId);
        return queryChain().select(SysMenuTableDef.SYS_MENU.MENU_ID)
            .from(SysMenu.class)
            .leftJoin(SysRoleMenu.class)
            .on(SysMenuTableDef.SYS_MENU.MENU_ID.eq(SysRoleMenuTableDef.SYS_ROLE_MENU.MENU_ID))
            .where(SysRoleMenuTableDef.SYS_ROLE_MENU.ROLE_ID.eq(roleId))
            .and(SysMenuTableDef.SYS_MENU.MENU_ID.notIn(query()
                .select(SysMenuTableDef.SYS_MENU.PARENT_ID)
                .from(SysMenu.class)
                .innerJoin(SysRoleMenu.class)
                .on(SysMenuTableDef.SYS_MENU.MENU_ID.eq(SysRoleMenuTableDef.SYS_ROLE_MENU.MENU_ID)
                    .and(SysRoleMenuTableDef.SYS_ROLE_MENU.ROLE_ID.eq(roleId))), sysRole.getMenuCheckStrictly()))
            .listAs(Long.class);
    }

    /**
     * 通过menuId查询单条数据
     *
     * @param menuId 主键
     * @return 实例对象
     */
    @Override
    public SysMenuVo queryById(Long menuId) {
        return getOneAs(query().eq(SysMenu::getMenuId, menuId), SysMenuVo.class);
    }

    /**
     * 新增数据
     *
     * @param sysMenuBo 实例对象
     * @return 实例对象
     */
    @Override
    public Boolean insertByBo(SysMenuBo sysMenuBo) {
        SysMenu sysMenu = MapstructUtils.convert(sysMenuBo, SysMenu.class);
        return save(sysMenu);
    }

    /**
     * 修改数据
     *
     * @param sysMenuBo 实例对象
     * @return 实例对象
     */
    @Override
    public Boolean updateByBo(SysMenuBo sysMenuBo) {
        SysMenu sysMenu = MapstructUtils.convert(sysMenuBo, SysMenu.class);
        return updateById(sysMenu);
    }

    /**
     * 通过主键删除数据
     *
     * @param menuId 主键
     * @return 是否成功
     */
    @Override
    public Boolean deleteWithValidByIds(Long menuId) {
        return removeById(menuId);
    }

    /**
     * 查询用户菜单
     *
     * @param userId 用户ID
     * @return 菜单信息
     */
    @Override
    public List<SysMenu> queryMenuTreeByUserId(Long userId) {
        List<SysMenu> sysMenus;
        if (LoginHelper.isSuperAdmin(userId)) {
            sysMenus = selectMenuTreeAll();
        } else {
            sysMenus = selectMenuTreeByUserId(userId);
        }
        return getChildPerms(sysMenus);
    }

    /**
     * 构建前端下拉树结构
     *
     * @param menus 菜单列表
     * @return 下拉结构
     */
    @Override
    public List<RouterVo> buildMenuTree(List<SysMenu> menus) {
        List<RouterVo> routers = new LinkedList<>();
        for (SysMenu menu : menus) {
            RouterVo routerVo = new RouterVo();
            routerVo.setHidden(UserConstants.MENU_DISABLE.equals(menu.getVisible()));
            routerVo.setName(menu.getRouteName());
            routerVo.setPath(menu.getRouterPath());
            routerVo.setComponent(menu.getComponentInfo());
            routerVo.setQuery(menu.getQueryParam());
            routerVo.setMeta(new MetaVo(menu.getMenuName(),
                menu.getIcon(),
                StringUtils.equals(UserConstants.MENU_DISABLE, menu.getIsCache()),
                menu.getPath()));
            List<SysMenu> cMenus = menu.getChildren();
            if (CollUtil.isNotEmpty(cMenus) && UserConstants.MENU_TYPE_DIR.equals(menu.getMenuType())) {
                routerVo.setAlwaysShow(true);
                routerVo.setRedirect("noRedirect");
                routerVo.setChildren(buildMenuTree(cMenus));
            } else if (menu.isMenuFrame()) {
                routerVo.setMeta(null);
                List<RouterVo> childrenList = new ArrayList<>();
                RouterVo children = new RouterVo();
                children.setPath(menu.getPath());
                children.setComponent(menu.getComponent());
                children.setName(StringUtils.capitalize(menu.getPath()));
                children.setMeta(new MetaVo(menu.getMenuName(),
                    menu.getIcon(),
                    StringUtils.equals("1", menu.getIsCache()),
                    menu.getPath()));
                children.setQuery(menu.getQueryParam());
                childrenList.add(children);
                routerVo.setChildren(childrenList);
            } else if (menu.getParentId().intValue() == 0 && menu.isInnerLink()) {
                routerVo.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
                routerVo.setPath("/");
                List<RouterVo> childrenList = new ArrayList<>();
                RouterVo children = new RouterVo();
                String routerPath = SysMenu.innerLinkReplaceEach(menu.getPath());
                children.setPath(routerPath);
                children.setComponent(UserConstants.INNER_LINK);
                children.setName(StringUtils.capitalize(routerPath));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
                childrenList.add(children);
                routerVo.setChildren(childrenList);
            }
            routers.add(routerVo);
        }
        return routers;
    }

    /**
     * 根据父节点ID获取子节点 默认父节点是0
     *
     * @param list 菜单列表
     * @return 查询结果
     */
    private List<SysMenu> getChildPerms(List<SysMenu> list) {
        List<SysMenu> returnList = new ArrayList<>();
        for (SysMenu t : list) {
            if (t.getParentId() == 0L) {
                recursionFn(list, t);
                returnList.add(t);
            }
        }
        return returnList;
    }

    /**
     * 递归列表
     *
     * @param list 菜单信息
     * @param t    临时菜单
     */
    private void recursionFn(List<SysMenu> list, SysMenu t) {
        List<SysMenu> childList = StreamUtils.filter(list, e -> e.getParentId().equals(t.getMenuId()));
        t.setChildren(childList);
        for (SysMenu tChild : childList) {
            if (list.stream().anyMatch(e -> e.getParentId().equals(tChild.getMenuId()))) {
                recursionFn(list, tChild);
            }
        }
    }

    /**
     * 查看所有菜单
     *
     * @return 菜单列表
     */
    private List<SysMenu> selectMenuTreeAll() {
        QueryWrapper query = query()
            .eq(SysMenu::getStatus, UserConstants.MENU_NORMAL)
            .in(SysMenu::getMenuType, UserConstants.MENU_TYPE_DIR, UserConstants.MENU_TYPE_MENU)
            .orderBy(SysMenu::getParentId, true)
            .orderBy(SysMenu::getOrderNum, true);
        return list(query);
    }

    /**
     * 根据用户ID查询菜单
     *
     * @param userId 用户ID
     * @return 菜单列表
     */
    private List<SysMenu> selectMenuTreeByUserId(Long userId) {
        return queryChain()
            .select(QueryMethods.distinct(SysMenuTableDef.SYS_MENU.DEFAULT_COLUMNS))
            .from(SysMenu.class)
            .leftJoin(SysRoleMenu.class)
            .on(SysMenuTableDef.SYS_MENU.MENU_ID.eq(SysRoleMenuTableDef.SYS_ROLE_MENU.MENU_ID).and(SysMenuTableDef.SYS_MENU.STATUS.eq(UserConstants.MENU_NORMAL)))
            .leftJoin(SysRole.class)
            .on(SysRoleTableDef.SYS_ROLE.ROLE_ID.eq(SysRoleMenuTableDef.SYS_ROLE_MENU.ROLE_ID).and(SysRoleTableDef.SYS_ROLE.ROLE_STATUS.eq(UserConstants.ROLE_NORMAL)))
            .where(SysMenuTableDef.SYS_MENU.MENU_TYPE.in(UserConstants.MENU_TYPE_DIR, UserConstants.MENU_TYPE_MENU))
            .and(SysRoleTableDef.SYS_ROLE.ROLE_ID.in(
                query()
                    .select(SysUserRoleTableDef.SYS_USER_ROLE.ROLE_ID)
                    .from(SysUserRole.class)
                    .where(SysUserRoleTableDef.SYS_USER_ROLE.USER_ID.eq(userId)))
            )
            .orderBy(SysMenuTableDef.SYS_MENU.PARENT_ID.asc(), SysMenuTableDef.SYS_MENU.ORDER_NUM.asc()).listAs(SysMenu.class);
    }
}
